import requests
import re
import json
import os
import sys

# ====== 配置区 ======
VIDEO_PAGE = 'https://www.bilibili.com/video/BV1YLLfz3EMt/'  # 你的目标B站视频链接


headers = {
    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
    'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,ar;q=0.7',
    'cache-control': 'no-cache',
    'pragma': 'no-cache',
    'priority': 'u=0, i',
    'referer': 'https://www.bilibili.com/',
    'sec-ch-ua': '"Not)A;Brand";v="8", "Chromium";v="138", "Microsoft Edge";v="138"',
    'sec-ch-ua-mobile': '?0',
    'sec-ch-ua-platform': '"Windows"',
    'sec-fetch-dest': 'document',
    'sec-fetch-mode': 'navigate',
    'sec-fetch-site': 'same-origin',
    'sec-fetch-user': '?1',
    'upgrade-insecure-requests': '1',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0',
    # 'cookie': "buvid3=C19CDB0A-C919-CE23-AB5F-E96BC4B067C090580infoc; b_nut=1744940390; _uuid=4B2E96C8-3A102-7E2C-2D8E-96DDF46434C1090021infoc; buvid4=06DD046B-935B-A1BD-5D79-C09A4D8BA84B91981-025041809-lReeRFsPXBLk%2FWgN%2FIMY7g%3D%3D; rpdid=|(J|~uR|J|)l0J'u~RRmR|Jmk; DedeUserID=399903144; DedeUserID__ckMd5=e7a3014eb79cd9c6; CURRENT_QUALITY=80; enable_web_push=DISABLE; enable_feed_channel=ENABLE; fingerprint=44ec6e7c0ebb84d364509656df535d2e; buvid_fp_plain=undefined; buvid_fp=44ec6e7c0ebb84d364509656df535d2e; header_theme_version=OPEN; theme-tip-show=SHOWED; LIVE_BUVID=AUTO6517521205297339; PVID=1; theme-avatar-tip-show=SHOWED; b_lsid=910DC1018F_1987313BA4A; bmg_af_switch=1; bmg_src_def_domain=i1.hdslb.com; bili_ticket=eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTQ1Mzc1MzcsImlhdCI6MTc1NDI3ODI3NywicGx0IjotMX0.wQm8NFgONfm86C4WKfh_xEFNm_Ur2LBz4A6ln1GZMPs; bili_ticket_expires=1754537477; SESSDATA=ef0e1d8c%2C1769830438%2C12442%2A81CjArpsf3cIANx3Wg3TtBgnMQ9VBoYCbxaTz1zEVWspPz37aG-LpP2rLYnErmHfrklbUSVkNKU0NLdTBCTFgydndRakZzdHBhSzJ0MVdMcjhUbWtTWmtOc0xPSEtEZVl3cENweHIyVUpVZWkzenNrZTR2eDNmaDRJTlJiV09HTzlJM25uTnh4UWlBIIEC; bili_jct=e07d569fe01f5415a53a936f548c8523; sid=4vvyua4r; home_feed_column=4; browser_resolution=1065-954; bp_t_offset_399903144=1097108718127939584; CURRENT_FNVAL=2000",
}
cookies = {
    'buvid3': 'C19CDB0A-C919-CE23-AB5F-E96BC4B067C090580infoc',
    'b_nut': '1744940390',
    '_uuid': '4B2E96C8-3A102-7E2C-2D8E-96DDF46434C1090021infoc',
    'buvid4': '06DD046B-935B-A1BD-5D79-C09A4D8BA84B91981-025041809-lReeRFsPXBLk%2FWgN%2FIMY7g%3D%3D',
    'rpdid': "|(J|~uR|J|)l0J'u~RRmR|Jmk",
    'enable_web_push': 'DISABLE',
    'enable_feed_channel': 'ENABLE',
    'fingerprint': '44ec6e7c0ebb84d364509656df535d2e',
    'buvid_fp_plain': 'undefined',
    'buvid_fp': '44ec6e7c0ebb84d364509656df535d2e',
    'header_theme_version': 'OPEN',
    'theme-tip-show': 'SHOWED',
    'LIVE_BUVID': 'AUTO6517521205297339',
    'PVID': '1',
    'theme-avatar-tip-show': 'SHOWED',
    'theme-switch-show': 'SHOWED',
    'CURRENT_QUALITY': '0',
    'SESSDATA': '1b0a8da7%2C1769915881%2Cf195e%2A81CjAPcVrd8lRIc3CpHDCX8jWBYczYZnQBhyyqSHlrSXQV4A3y9yPyv_ES87i4R5x5vaISVmF1MnVxZlZZdC1OM3dfLWtIWGVnYzd6bW1RVkQ0cF92dVNwZ1JhbmJJTks3MS1XcmszU2dndGdYaTY5LTltWjhxU2JiQ3NYNFIxakd2SnVqLTZucDZRIIEC',
    'bili_jct': 'f2141613390790f41f5aa8661b068017',
    'DedeUserID': '399903144',
    'DedeUserID__ckMd5': 'e7a3014eb79cd9c6',
    'home_feed_column': '4',
    'browser_resolution': '89-946',
    'b_lsid': 'D12ED482_19883853E71',
    'bsource': 'search_bing',
    'bili_ticket': 'eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTQ4MTI0MDYsImlhdCI6MTc1NDU1MzE0NiwicGx0IjotMX0.j0C7IWTcIZhXEBiYn_ScdtoBMxGdKqdqrKqvuEi1nbQ',
    'bili_ticket_expires': '1754812346',
    'CURRENT_FNVAL': '4048',
    'sid': '6u8got93',
    'bp_t_offset_399903144': '1098287467607359488',
}

# ====== 配置区结束 ======

def main():
    session = requests.Session()
    session.headers.update(headers)
    # session.cookies.update(cookies)

    # 1. 获取页面HTML
    print("正在获取视频页面...")
    resp = session.get(VIDEO_PAGE,cookies=cookies)
    if resp.status_code != 200:
        print("页面访问失败，状态码：", resp.status_code)
        sys.exit(1)
    html = resp.text

    # 2. 解析视频名
    title_match = re.search(r'<h1[^>]*title="([^"]+)"', html)
    video_name = title_match.group(1).strip().replace(" ", "") if title_match else "bilibili_video"
    safe_name = re.sub(r'[\/:*?"<>|]', "_", video_name)  # 防止Windows非法字符

    print("视频名：", safe_name)
    if not os.path.exists(safe_name):
        os.mkdir(safe_name)

    # 3. 提取window.__playinfo__的json
    m = re.search(r'window\.__playinfo__\s*=\s*({.+?})\s*</script>', html, re.S)
    if not m:
        print("没有找到window.__playinfo__结构，视频页面结构或Cookie有问题。")
        sys.exit(1)
    playinfo = json.loads(m.group(1))

    # 4. 获得视频流与音频流 url
    video_url = playinfo['data']['dash']['video'][0]['baseUrl']
    audio_url = playinfo['data']['dash']['audio'][0]['baseUrl']
    print("视频流：", video_url)
    print("音频流：", audio_url)

    # 5. 下载视频流
    print("\n正在下载视频流...")
    video_fn = os.path.join(safe_name, "video.m4s")
    download_file(session, video_url, VIDEO_PAGE, video_fn)

    # 6. 下载音频流
    print("\n正在下载音频流...")
    audio_fn = os.path.join(safe_name, "audio.m4s")
    download_file(session, audio_url, VIDEO_PAGE, audio_fn)

    # 7. 合成音视频
    output_fn = os.path.join(safe_name, f"{safe_name}_合成.mp4")
    print(f"\n正在合成音视频(需要本机已装好ffmpeg)：\n{output_fn}\n")
    merge_cmd = f'ffmpeg -y -i "{video_fn}" -i "{audio_fn}" -c copy "{output_fn}"'
    print(merge_cmd)
    rv = os.system(merge_cmd)
    if rv == 0:
        print("合成成功，已保存为：", output_fn)
    else:
        print("合成时ffmpeg报错，请确认ffmpeg在环境变量path中或写绝对路径。")


def download_file(session, url, referer, filename):
    headers = {
        'referer': referer,
        'user-agent': session.headers['user-agent']
    }
    resp = session.get(url, headers=headers, stream=True,cookies=cookies)
    total = int(resp.headers.get('content-length', 0))
    with open(filename, "wb") as f:
        dl = 0
        for chunk in resp.iter_content(chunk_size=1024 * 1024):
            if chunk:
                dl += len(chunk)
                f.write(chunk)
                done = int(50 * dl / total) if total else 0
                sys.stdout.write(f"\r[{'█' * done}{'.' * (50 - done)}] {dl / 1e6:.1f}MB/{total / 1e6:.1f}MB")
                sys.stdout.flush()
    print(f"\n{filename} 下载完成")


if __name__ == "__main__":
    main()